package org.jw.wishlist.config;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.system.SystemProperties;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.context.WebServerPortFileWriter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.core.log.LogMessage;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;

/**
 * 启动成功把端口写到文件上
 *
 * @author Jiawei Zhao
 * @see org.springframework.boot.context.ApplicationPidFileWriter
 * @see WebServerPortFileWriter
 */
public class ApplicationPortFileWriter implements ApplicationListener<WebServerInitializedEvent> {

  private static final Log logger = LogFactory.getLog(ApplicationPortFileWriter.class);

  private static final String DEFAULT_FILE_NAME = "application.port";

  private static final List<Property> FILE_PROPERTIES;

  static {
    List<Property> properties = new ArrayList<>();
    properties.add(new SpringProperty("spring.", "portfile"));
    properties.add(new SystemProperty("PORTFILE"));
    properties.add(new SystemProperty("portfile"));
    FILE_PROPERTIES = Collections.unmodifiableList(properties);
  }

  private final File file;

  public ApplicationPortFileWriter() {
    this(new File(DEFAULT_FILE_NAME));
  }

  /**
   * Create a new {@link ApplicationPortFileWriter} instance with a specified filename.
   *
   * @param filename the name of file containing port
   */
  public ApplicationPortFileWriter(String filename) {
    this(new File(filename));
  }

  /**
   * Create a new {@link ApplicationPortFileWriter} instance with a specified file.
   *
   * @param file the file containing port
   */
  public ApplicationPortFileWriter(File file) {
    this.file = file;
  }

  @Override
  public void onApplicationEvent(WebServerInitializedEvent event) {
    File portFile = getPortFile(event);
    try {
      String port = String.valueOf(event.getWebServer().getPort());
      createParentFolder(portFile);
      FileCopyUtils.copy(port.getBytes(), portFile);
      portFile.deleteOnExit();
    } catch (Exception ex) {
      logger.warn(LogMessage.format("Cannot create port file %s", this.file));
    }
  }

  /**
   * Return the actual port file that should be written for the given application context. The default implementation
   * builds a file from the source file and the application context namespace if available.
   *
   * @param event the event
   * @return the file that should be written
   */
  protected File getPortFile(WebServerInitializedEvent event) {
    File portFile = this.file;

    String override = getProperty(event, FILE_PROPERTIES);
    if (override != null) {
      portFile = new File(override);
    }

    String namespace = getServerNamespace(event.getApplicationContext());
    if (StringUtils.isEmpty(namespace)) {
      return portFile;
    }
    String name = portFile.getName();
    String extension = StringUtils.getFilenameExtension(portFile.getName());
    extension = StringUtils.hasText(extension) ? extension : "";
    int substringTo = StringUtils.hasLength(extension) ? extension.length() - 1 : 0;
    name = name.substring(0, name.length() - substringTo);
    if (isUpperCase(name)) {
      name = name + "-" + namespace.toUpperCase(Locale.ENGLISH);
    } else {
      name = name + "-" + namespace.toLowerCase(Locale.ENGLISH);
    }
    if (StringUtils.hasLength(extension)) {
      name = name + "." + extension;
    }
    return new File(portFile.getParentFile(), name);
  }

  private String getServerNamespace(ApplicationContext applicationContext) {
    if (applicationContext instanceof WebServerApplicationContext) {
      return ((WebServerApplicationContext) applicationContext).getServerNamespace();
    }
    return null;
  }

  private boolean isUpperCase(String name) {
    for (int i = 0; i < name.length(); i++) {
      if (Character.isLetter(name.charAt(i)) && !Character.isUpperCase(name.charAt(i))) {
        return false;
      }
    }
    return true;
  }

  private void createParentFolder(File file) {
    File parent = file.getParentFile();
    if (parent != null) {
      parent.mkdirs();
    }
  }

  private String getProperty(WebServerInitializedEvent event, List<Property> candidates) {
    for (Property candidate : candidates) {
      String value = candidate.getValue(event);
      if (value != null) {
        return value;
      }
    }
    return null;
  }

  /**
   * Provides access to a property value.
   */
  private interface Property {

    String getValue(WebServerInitializedEvent event);

  }

  /**
   * {@link Property} obtained from Spring's {@link Environment}.
   */
  private static class SpringProperty implements Property {

    private final String prefix;

    private final String key;

    SpringProperty(String prefix, String key) {
      this.prefix = prefix;
      this.key = key;
    }

    @Override
    public String getValue(WebServerInitializedEvent event) {
      Environment environment = event.getApplicationContext().getEnvironment();
      return environment.getProperty(this.prefix + this.key);
    }

  }

  /**
   * {@link Property} obtained from {@link SystemProperties}.
   */
  private static class SystemProperty implements Property {

    private final String[] properties;

    SystemProperty(String name) {
      this.properties = new String[]{name.toUpperCase(Locale.ENGLISH), name.toLowerCase(Locale.ENGLISH)};
    }

    @Override
    public String getValue(WebServerInitializedEvent event) {
      return SystemProperties.get(this.properties);
    }

  }

}
